package info.niwota.group.riff;

import info.niwota.group.AudioRecorder;
import info.niwota.group.Constants;
import info.niwota.group.FileFormats;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;

import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.MediaRecorder;
import android.util.Log;

/**
 * 
 * @author qiangli
 * 
 */
public class WaveRecorder implements AudioRecorder, Runnable {

	private static final String TAG = "WaveRecorder";

	private final Object mutex = new Object();

	private final int audioEncoding = AudioFormat.ENCODING_PCM_16BIT; // PCM_8BIT
	// not
	// supported
	private int sampleRate = 8000; // 5900, 8000, 11025, 16000, 22050, 44100
	private final int channelConfiguration = AudioFormat.CHANNEL_CONFIGURATION_MONO;

	private volatile boolean isPaused;

	private File file;

	private volatile boolean isRecording;

	private int audioSource = MediaRecorder.AudioSource.MIC;

	private Thread thread;

	private int maxAmplitude;

	private int bufSize;

	public WaveRecorder() {
		super();
	}

	public void run() {
		if (Constants.DEBUG_ENABLED) {
			Log.d(TAG, "recording starting...");
		}
		synchronized (mutex) {
			while (!this.isRecording) {
				try {
					mutex.wait();
				} catch (InterruptedException e) {
					throw new IllegalStateException("Wait() interrupted!", e);
				}
			}
		}
		android.os.Process
				.setThreadPriority(android.os.Process.THREAD_PRIORITY_URGENT_AUDIO);
		AudioRecord record = new AudioRecord(audioSource, sampleRate,
				channelConfiguration, audioEncoding, bufSize);
		//
		byte[] buf = new byte[bufSize];
		//
		UlawEncoderInputStream uis = new UlawEncoderInputStream(
				new AudioRecordInputStream(record, bufSize));
		RandomAccessFile raf = null;
		try {
			raf = new RandomAccessFile(file, "rw");
			raf.seek(WaveHeader.HEADER_LENGTH);
		} catch (IOException e) {
			throw new IllegalStateException("open file", e);
		}
		//
		int total = 0;
		record.startRecording();
		//
		while (this.isRecording) {
			synchronized (mutex) {
				if (this.isPaused) {
					try {
						mutex.wait(250);
					} catch (InterruptedException e) {
						throw new IllegalStateException("wait", e);
					}
					continue;
				}
			}

			try {
				int cnt = uis.read(buf, 0, bufSize);
				maxAmplitude = uis.getMaxAbsolutePcm();
				//
				if (Constants.DEBUG_ENABLED) {
					Log.d(TAG, "amplitude: " + maxAmplitude + " cnt: " + cnt
							+ " total: " + total);
				}
				//
				if (cnt > 0) {
					total += cnt;
					raf.write(buf, 0, cnt);
				}
			} catch (IOException e) {
				throw new IllegalStateException("write", e);
			}
		}
		record.stop();
		//
		if (Constants.DEBUG_ENABLED) {
			Log.d(TAG, "write WAV header buf size: " + bufSize + " total: "
					+ total + " sampleRate: " + sampleRate);
		}
		// write WAV header
		try {
			WaveHeader head = new WaveHeader(sampleRate);
			head.setNumBytes(total);
			//
			ByteArrayOutputStream bos = new ByteArrayOutputStream(
					WaveHeader.HEADER_LENGTH);
			head.write(bos);
			//
			raf.seek(0);
			raf.write(bos.toByteArray());
		} catch (IOException e) {
			throw new IllegalStateException("write header", e);
		}
		try {
			raf.close();
			uis.close();
		} catch (IOException e) {
			throw new IllegalStateException("close", e);
		}
		if (Constants.DEBUG_ENABLED) {
			Log.d(TAG, "recording stopped");
		}
	}

	private void setRecording(boolean isRecording) {
		synchronized (mutex) {
			this.isRecording = isRecording;
			if (this.isRecording) {
				mutex.notify();
			}
		}
	}

	// private boolean isRecording() {
	// synchronized (mutex) {
	// return isRecording;
	// }
	// }

	private void setPaused(boolean isPaused) {
		synchronized (mutex) {
			this.isPaused = isPaused;
		}
	}

	// private boolean isPaused() {
	// synchronized (mutex) {
	// return isPaused;
	// }
	// }

	public int getMaxAmplitude() {
		if (isRecording) {
			return maxAmplitude;
		}
		return 0;
	}

	public void prepare() throws Exception {
		setPaused(false);
		maxAmplitude = 0;
		//
		if (file == null) {
			throw new IllegalStateException("file name is null");
		}
		if (file.exists()) {
			file.delete();
		}
		try {
			file.createNewFile();
		} catch (IOException e) {
			throw new IllegalStateException("Cannot create file: "
					+ file.toString());
		}
		try {
			bufSize = getBufferSize();
			//
			bufSize = (bufSize * 2);
		} catch (Exception e) {
			throw e;
		}
		//
		thread = new Thread(this);
	}

	private int getBufferSize() {
		int rate = sampleRate;
		// user preferred
		int size = AudioRecord.getMinBufferSize(rate, channelConfiguration,
				audioEncoding);
		if (size > 0) {
			return size;
		}
		// try fall back on a smaller value
		final int[] rates = { 8000, 11025, 16000, 22050, 44100 };
		rate = (rate < 8000 ? 11025 : rate); //
		for (int i = rates.length - 1; i >= 0; i--) {
			int ri = rates[i];
			if (ri < rate) {
				size = AudioRecord.getMinBufferSize(ri,
						channelConfiguration, audioEncoding);
				if (size > 0) {
					sampleRate = ri;
					if (Constants.DEBUG_ENABLED) {
						Log.d(TAG, "getBufferSize size: " + size
								+ " sampleRate: " + sampleRate);
					}
					return size;
				}
			}
		}
		throw new IllegalStateException("Can't allocate buffer " + sampleRate);
	}

	public void release() {
		clear();
	}

	private void clear() {
		setRecording(false);
		if (thread != null) {
			try {
				thread.interrupt();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		maxAmplitude = 0;
		thread = null;
	}

	public void reset() {
		clear();
		thread = new Thread(this);
	}

	public void setAudioEncoder(int encoder) {
		// 16BIT only
	}

	public void setAudioSource(int audioSource) {
		this.audioSource = audioSource;
	}

	public void setOutputFile(String fileName) {
		this.file = new File(fileName);
	}

	public void setOutputFormat(String mimeType) {
		if (Constants.DEBUG_ENABLED) {
			if (!FileFormats.AUDIO_WAV.equals(mimeType)) {
				throw new RuntimeException("Only RIFF/WAVE supported: "
						+ mimeType);
			}
		}
		// RIFF/WAVE only
	}

	public void start() {
		setRecording(true);
		//
		thread.start();
	}

	public void stop() {
		setRecording(false);
		maxAmplitude = 0;
	}

	public void setSampleRate(int hz) {
		this.sampleRate = hz;
	}

	public int getBitRate() {
		return (sampleRate * 8 * 1);
	}
}
